
package com.cosc645.ecash;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import static java.lang.System.exit;

public class Bank {
    public RSAPrivateKey privateKey;
    public RSAPublicKey publicKey;
    private HashMap<BigInteger, Boolean> moneyOrderCarbonCopies;
    public HashMap<Class, Double> accounts;

    private String bankSignature = "BANKSIGAHAHAHA";

    Bank() {
        KeyPairGenerator kpg = null;
        try {
            kpg = KeyPairGenerator.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        kpg.initialize(2048);
        KeyPair keyPair = kpg.genKeyPair();

        privateKey = (RSAPrivateKey) keyPair.getPrivate();
        publicKey = (RSAPublicKey) keyPair.getPublic();

        moneyOrderCarbonCopies = new HashMap<BigInteger, Boolean>();


        //Create accounts with balance
        accounts = new HashMap<Class, Double>();
        accounts.put(Alice.class, 100.00);
        accounts.put(Bob.class, 0.00);
    }

    /**
     * Get amount that belongs to customer.
     *
     * @param customer
     * @return
     */
    public double getAmount(Class customer) {
        return accounts.get(customer);
    }

    BigInteger checksMoneyOrders(Alice alice, double amount) {
        int lastOne = (alice.getBlindedMoneyOrders().size() - 1);
        BigInteger goodMoneyOrder = null;
        System.out.println("Bank is inspecting " + (alice.getBlindedMoneyOrders().size() - 1) + " money orders");
        boolean flag = true;
        int i = 0;
        for (BigInteger blindMoneyOrder : alice.getBlindedMoneyOrders()) {
            BigInteger b = alice.getBlindedMoneyOrders().get(i);
            // BigInteger unblind = alice.unblind(publicKey, b);
            BigInteger unblind = alice.unblindUnOfficial(publicKey, b);
            try {
                MoneyOrder currentMoneyOrder = new MoneyOrder(new String(unblind.toByteArray(), "UTF-8"));
                if (currentMoneyOrder.getAmount() != amount && i != lastOne + 1) {
                    flag = false;
                } else {
                    goodMoneyOrder = b;
                }
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }


            i++;
        }

        if (flag) {
            //sign moneyorder
            System.out.println("The bank found that Alice did not cheat. The bank is signing the last money order.");
            System.out.println("The bank is deducting " + amount + " from Alice's account.");
            double aliceBalance = accounts.get(Alice.class);
            aliceBalance = aliceBalance - amount;
            accounts.put(Alice.class, aliceBalance);

        } else {
            System.out.println("The bank found that alice is cheating.  The money orders were not all for the same amount.");
            exit(0);
        }
        return goodMoneyOrder;
    }

    public boolean checkMoneyOrderSignature(MoneyOrder mo) {
        return !seenSerialNumberBefore();
    }

    private boolean seenSerialNumberBefore() {
        //TODO: implement something
        return false;
    }

    public String getBankSignature() {
        return bankSignature;
    }

    public RSAPublicKey getPublicKey() {
        return publicKey;
    }

    /**
     * Bank signs by computing t: s^d mod (n) send to alice
     *
     * @param s
     * @return
     */
    public BigInteger signMoneyOrder(BigInteger s, Double amount) {
        BigInteger d = privateKey.getPrivateExponent();
        BigInteger n = publicKey.getModulus();
//        s = alice.unblindUnOfficial(publicKey, s);
//        System.out.println("Cheat: " + s);
        BigInteger bankComputesT = s.modPow(d, n);
        moneyOrderCarbonCopies.put(bankComputesT, false);
        return bankComputesT;

    }

    public void cashMoneyOrder(BigInteger t, BigInteger unblindedT, BigInteger bobsM, Bob bob) {
        BigInteger m = unblindedT.modPow(publicKey.getPublicExponent(), publicKey.getModulus());
        System.out.println("CASHM = " + m);
        System.out.println("bobsM = " + bobsM);
        //System.out.println(m.equals(bobsM));
        if (m.equals(bobsM)) {
            MoneyOrder currentMoneyOrder = null;
            try {
                currentMoneyOrder = new MoneyOrder(new String(m.toByteArray(), "UTF-8"));
            } catch (UnsupportedEncodingException ex) {
                Logger.getLogger(Bank.class.getName()).log(Level.SEVERE, null, ex);
            }

            if (moneyOrderCarbonCopies.containsKey(t)) {
                if (!moneyOrderCarbonCopies.get(t)) {
                    System.out.println("m = bobsM is true");
                    System.out.println("Carbon copy found? = " + moneyOrderCarbonCopies.containsKey(t));
                    if (moneyOrderCarbonCopies.containsKey(t)) {
                        System.out.println("Money Order is for $" + currentMoneyOrder.getAmount());
                        System.out.println("Adding money to Bob's account...");
                        double bobBalance = accounts.get(Bob.class);
                        bobBalance = bobBalance + currentMoneyOrder.getAmount();
                        accounts.put(Bob.class, bobBalance);
                        System.out.println("Bob's balance is now: $" + accounts.get(Bob.class));
                        moneyOrderCarbonCopies.put(t, true);
                    } else {
                        System.out.println("BANK HAS NOT SIGNED THIS MONEY ORDER BEFORE!!  BOB IS CHEATING!!!");
                    }
                } else {
                    //alice has cheated and we reconstruct her identity
                    StringXorer sor = new StringXorer();
                    System.out.println(sor.decode(currentMoneyOrder.getL0(), currentMoneyOrder.getR0()) + " is possibly a cheater!!");

                }
            } else {
                System.out.println("Bank did not sign this money order");
            }
        } else {
            System.out.println("m is not equalt to bobsM: invalid money order");
        }
    }

    private HashMap<BigInteger, Boolean> cashedMoneyOrders = new HashMap<BigInteger, Boolean>();

}
